package com.caishi.lkx.exam.records.oss;

import cn.hutool.core.bean.BeanUtil;
import com.caishi.lkx.exam.model.ExamQuestionRank;
import com.caishi.lkx.exam.model.PaperRecordsModel;
import com.caishi.lkx.exam.model.QuestionModel;
import com.caishi.lkx.exam.records.dto.*;
import com.caishi.lkx.exam.service.*;
import com.caishi.lkx.exam.vo.PaperResultVo;
import com.caishi.lkx.user.service.IUserQuestionRecordsService;
import com.zzw.common.threadpool.SysThreadPool;
import com.zzw.common.entity.impl.BSEntity;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.lock.RedissonLock;
import com.caishi.lkx.exam.ExamResultCode;
import com.caishi.lkx.exam.QuestionCollectionBlockItem;
import com.caishi.lkx.exam.dto.ApiQuestionRecordsDtoItem;
import com.caishi.lkx.exam.ienum.type.PaperRecordType;
import com.caishi.lkx.exam.ienum.type.QuestionCollectionType;
import com.caishi.lkx.exam.ienum.type.QuestionType;

import com.caishi.lkx.exam.question.QuestionDataProcessComposite;
import com.caishi.lkx.exam.records.RecordsCollectType;
import com.caishi.lkx.exam.records.RecordsService;
import com.caishi.lkx.exam.util.QuestionUtil;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OssRecordsService implements RecordsService {


    @Resource
    private IQuestionService questionService;

    @Resource
    private IChapterService chapterService;

    @Resource
    private IPaperService paperService;

    @Resource
    private IPaperRecordsService paperRecordsService;

    @Resource
    private OssFileService fileService;

    @Resource
    private IExamQuestionRankService examQuestionRankService;

    @Resource
    private IWeekRankService weekRankService;

    @Resource
    private IUserCountRecordsService userCountRecordsService;

    @Resource
    private IUserQuestionRecordsService userQuestionRecordsService;

    private static final QuestionDataProcessComposite questionDataProcessComposite = QuestionDataProcessComposite.instance;

    //==================================    RecordsService
    @Override
    public boolean userAnswerIsRight(QuestionType type, String userId, String collectId, String rightAnswerBase64, Object answer) {
        return questionDataProcessComposite.userAnswerIsRight(type, questionService.answerDecrypt(rightAnswerBase64, userId, collectId), answer);
    }

    @Override
    public int userPushOneQuestionRecords(String userId, String industryCode, String examId, Long categoryId, String qid, NewRecordsDtoItem item, boolean right, int rightCount) {
        if (item.getCt().type2CollectType() == QuestionCollectionType.paper)
            throw new BizRuntimeException();
        var result = fileService.getQuestionRecordsResult(userId, industryCode, examId, categoryId, item.getCt(), item.getCid());
        var oldData = result.get(qid);
        //  1 以前对 现在错 2以前错现在对 3新纪录对 4新纪录错
        byte type = 0;
        if (oldData != null) {
            type = -1;
            if (oldData.isRt() && !right) {
                // 以前对 现在错
                type = 1;
            } else if (!oldData.isRt() && right) {
                // 2以前错 现在对
                type = 2;
            }
        }
        if (type == 0) {
            type = (byte) (right ? 3 : 4);
        }
        var d = new RecordsCountRecordDto();
        if (type == 1 || type == 4) {
            // 提交错题记录
            pushErrorBlockData(userId, industryCode, examId, categoryId, item.getCt(), item.getCid(), new HashMap<>() {{
                put(qid, item);
            }});
            d.setEc(1);
        }
        d.setDc(type == 3 || type == 4 ? 1 : 0);
        if (type == 2) {
            // 移除错题记录 计算作对了几次
            var data = fileService.getErrorInCollectDataResult(userId, industryCode, examId, categoryId, item.getCt(), item.getCid());
            var count = data.get(qid);
            if (rightCount > 0 && count != null && count + 1 >= rightCount) {
                // 对了5次后移除错题
                data.remove(qid);
                fileService.setErrorInCollectData(userId, data, industryCode, examId, categoryId, item.getCt(), item.getCid());
                var errorData = fileService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
                errorData.remove(qid);
                fileService.setErrorRecordsData(userId, errorData, industryCode, examId, categoryId);
                type = 5;
                d.setEc(-1);
            } else if (count != null) {
                data.put(qid, count + 1);
                fileService.setErrorInCollectData(userId, data, industryCode, examId, categoryId, item.getCt(), item.getCid());
            }
        }
        // -1 无操作 1 以前对 现在错 2以前错现在对 3新纪录对 4新纪录错 5移除错题
        if (type == -1 || type == 2) return type;
        var data = new QuestionRecordsDtoItem();
        data.setRt(right);
        data.setT(item.getT());
        data.setAs(item.getAs());
        result.put(qid, data);
        fileService.setQuestionRecordsData(userId, result, industryCode, examId, categoryId, item.getCt(), item.getCid());
        collectCountRecordsChange(userId, industryCode, examId, categoryId, item.getCt(), item.getCid(), d);
        if (type == 4 || type == 5) {
            var newError = fileService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
            if (type == 4) {
                newError.put(qid, item);
                fileService.setErrorRecordsData(userId, newError, industryCode, examId, categoryId);
            } else if (newError.remove(qid) != null) {
                fileService.setErrorRecordsData(userId, newError, industryCode, examId, categoryId);
            }
        }
        return type;
    }

    @SneakyThrows
    @Override
    public void collectCountRecordsChange(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id, RecordsCountRecordDto data) {
        if (data.empty()) return;
        List<String> allIds = new LinkedList<>();
        if (type == RecordsCollectType.chapter) {
            allIds = chapterService.allParentIds(id);
        }
        allIds.add(id);
        for (String cid : allIds) {
            RedissonLock.lock("collectCountRecordsChange:" + userId + id , k -> {
                var da = fileService.getCollectCountRecordsResult(userId, industryCode, examId, categoryId, type, cid);
                da.setDc(Math.max(da.getDc() + data.getDc(), 0));
                da.setEc(Math.max(da.getEc() + data.getEc(), 0));
                da.setCc(Math.max(da.getCc() + data.getCc(), 0));
                fileService.setCollectCountRecordsData(userId, da, industryCode, examId, categoryId, type, cid);
            });
        }
        changeCategoryCountData(userId, data, industryCode, examId, categoryId);
    }

    @Override
    public PaperResultVo userPushPaperRecords(String userId, String industryCode, String examId, Long categoryId, String recordsId, Map<String, ApiQuestionRecordsDtoItem> data, LocalDateTime endTime, Long seconds) {
        var paperRecords = paperRecordsService.getById(recordsId);
        var recordsCollectType = RecordsCollectType.paperRecordType2Type(paperRecords.getType());
        if (paperRecords.getHaveFinish() || recordsCollectType == null) {
            throw new BizRuntimeException(ExamResultCode.paperAlreadySubmit);
        }
        var res = userPushPaperRecords(paperRecords, data, endTime, seconds);
        Map<String, NewRecordsDtoItem> errorData = (Map<String, NewRecordsDtoItem>) res[2];
        QuestionRecordsDto dto = (QuestionRecordsDto) res[1];
        fileService.setQuestionRecordsData(userId, dto, industryCode, examId, categoryId, recordsCollectType, recordsId);
        paperRecordsService.update(paperRecords);
        if (paperRecords.getType() == PaperRecordType.paperLibrary) {
            paperService.addNewStudent(paperRecords.getPaperId());
        }
        // 保存错题数量 做题数量
        SysThreadPool.execute(() -> {
            userCountRecordsService.addCount(userId, errorData.size(), data.size());
            userCountRecordsService.addPaper(userId);
            // 保存最新最提记录
            var categoryCollectRecordsDtoItem = new CategoryCollectRecordsDtoItem();
            categoryCollectRecordsDtoItem.setN(paperRecords.getName());
            categoryCollectRecordsDtoItem.setT(System.currentTimeMillis());
            categoryCollectRecordsDtoItem.setTc(paperRecords.getQcount());
            categoryCollectRecordsDtoItem.setDc(data.size());
            categoryCollectRecordsDtoItem.setEc(errorData.size());
            addCategoryNewCollectRecords(categoryCollectRecordsDtoItem, recordsCollectType, recordsId, userId, industryCode, examId, categoryId);
            if (!errorData.isEmpty()) {
                pushErrorBlockData(userId, industryCode, examId, categoryId, recordsCollectType, recordsId, errorData);
            }
            Map<String, Integer> error = new HashMap<>();
            Map<String, Integer> all = new HashMap<>();
            data.forEach((k, v) -> {
                var o = all.get(v.getTr());
                all.put(v.getTr(), o == null ? 1 : o + 1);
                if (!v.isRt()) {
                    var o1 = error.get(v.getTr());
                    error.put(v.getTr(), o1 == null ? 1 : o1 + 1);
                }
                if (recordsCollectType == RecordsCollectType.paperRecordPaperLibrary) {
                    var rankData = ExamQuestionRank.ExamQuestionRankBaseModel.builder()
                            .examId(examId)
                            .build();
                    rankData.setDoCount(1L);
                    rankData.setErrorCount(v.isRt() ? 0L : 1L);
                    rankData.setQuestionId(k);
                    rankData.setQuestionSign(v.getSn());
                    rankData.setCollectName(paperRecords.getName());
                    rankData.setCollectType(recordsCollectType.type2CollectType());
                    rankData.setCollectId(paperRecords.getPaperId());
                    rankData.setStem(v.getSt());
                    rankData.setType(v.getTr());
                    rankData.setCategoryId(categoryId);
                    examQuestionRankService.addDoCount(rankData, recordsCollectType);
                }
            });
            all.forEach((key, val) -> userQuestionRecordsService.setQuestionRecords(userId, categoryId, key, val, error.get(key) == null ? 0 : error.get(key)));
            weekRankService.addData(userId, categoryId, data.size(), errorData.size());
        });
        var countData = new RecordsCountRecordDto();
        countData.setDc(dto.getData().size());
        countData.setEc(errorData.size());
        collectCountRecordsChange(userId, industryCode, examId, categoryId, recordsCollectType, recordsId, countData);
        return new PaperResultVo(paperRecords, null, dto);
    }

    @Override
    public PaperResultVo userPushPaperRecords(String industryCode, String examId, Long categoryId, PaperRecordsModel paperRecords, Map<String, ApiQuestionRecordsDtoItem> data, LocalDateTime endTime, Long seconds) {
        var res = userPushPaperRecords(paperRecords, data, endTime, seconds);
        return new PaperResultVo((PaperRecordsModel) res[0], null, (QuestionRecordsDto) res[1]);
    }

    private Object[] userPushPaperRecords(PaperRecordsModel paperRecords, Map<String, ApiQuestionRecordsDtoItem> data, LocalDateTime endTime, Long seconds) {
        var recordsCollectType = RecordsCollectType.paperRecordType2Type(paperRecords.getType());
        HashMap<String, QuestionRecordsDtoItem> mapData = new HashMap<>(data.size() * 2);
        Map<String, NewRecordsDtoItem> errorData = new LinkedHashMap<>();
        data.forEach((key, value) -> {
            var item = new QuestionRecordsDtoItem();
            if (!value.isRt()) {
                var errorItem = new NewRecordsDtoItem();
                BeanUtil.copyProperties(value, errorItem);
                errorItem.setCn(paperRecords.getName());
                errorItem.setCid(paperRecords.getId());
                errorItem.setCt(recordsCollectType);
                errorData.put(key, errorItem);
            }
            item.setRt(value.isRt());
            item.setT(value.getT());
            item.setAs(value.getAs());
            mapData.put(key, item);
        });
        var dto = new QuestionRecordsDto();
        // 生成实体 分数映射
        Map<String, BigDecimal> questionScoreMap = paperRecords.getContent().stream()
                .flatMap(questionCollectionBlockItem -> questionCollectionBlockItem.getQs().stream())
                .collect(Collectors.toMap(QuestionCollectionBlockItem.QuestionCollectionItem::getId, QuestionCollectionBlockItem.QuestionCollectionItem::getScore, (v1, v2) -> v1.equals(BigDecimal.ZERO) ? v2 : v1));
        // 获得分数
        BigDecimal rightScore = new BigDecimal(0);
        for (var entity : mapData.entrySet().stream().filter(v -> v.getValue().isRt()).toList()) {
            var k = questionScoreMap.get(entity.getKey());
            if (QuestionUtil.questionIdIsCombinationChild(entity.getKey())) {
                k = questionService.selectOne(questionService.wrappers()
                        .select(QuestionModel::getScore)
                        .eq(BSEntity::getId, entity.getKey())).getScore();
            }
            rightScore = rightScore.add(k);
        }
        paperRecords.setScore(rightScore.floatValue());
        paperRecords.setHp(rightScore.floatValue() >= paperRecords.getPassScore());
        paperRecords.setEndTime(endTime);
        paperRecords.setSeconds(seconds);
        paperRecords.setErrorCount((int) mapData.values().stream().filter(v -> !v.isRt()).count());
        paperRecords.setDoCount(mapData.values().size());
        dto.getData().putAll(mapData);
        return new Object[]{paperRecords, dto, errorData};
    }

    @Override
    public void userSavePaperRecords(String userId, String industryCode, String examId, Long categoryId, String recordsId, Map<String, QuestionRecordsDtoItem> content, Long seconds, String lastId) {
        var model = paperRecordsService.getById(recordsId);
        var dto = new QuestionRecordsDto();
        dto.getData().put(QuestionRecordsDto.LAST_ID, lastId);
        var paperRecords = new PaperRecordsModel();
        dto.getData().putAll(content);
        paperRecords.setId(recordsId);
        paperRecords.setSeconds(seconds);
        paperRecordsService.update(paperRecords);
        fileService.setQuestionRecordsData(userId, dto, industryCode, examId, categoryId, RecordsCollectType.paperRecordType2Type(model.getType()),recordsId);
    }

    @Override
    public void pushErrorBlockData(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id, Map<String, NewRecordsDtoItem> errorData) {
        var newErrorData = fileService.getErrorRecordsResult(userId, industryCode, examId, categoryId);
        var collectErrorData = fileService.getErrorInCollectDataResult(userId, industryCode, examId, categoryId, type, id);
        errorData.forEach((k, v) -> {
            newErrorData.put(k, v);
            var oldCount = collectErrorData.get(k);
            collectErrorData.put(k, oldCount == null ? 0 : oldCount);
        });
        fileService.setErrorInCollectData(userId, collectErrorData, industryCode, examId, categoryId, type, id);
        fileService.setErrorRecordsData(userId, newErrorData, industryCode, examId, categoryId);
    }

    @Override
    public void pushCollectBlockData(String userId, String industryCode, String examId, Long categoryId, RecordsCollectType type, String id, Map<String, NewRecordsDtoItem> collectData, List<String> removeIds) {
        var newCollectData = fileService.getCollectRecordsResult(userId, industryCode, examId, categoryId);
        var oldCollect = fileService.getCollectInCollectDataResult(userId, industryCode, examId, categoryId, type, id);
        removeIds.forEach(k -> {
            newCollectData.remove(k);
            oldCollect.remove(k);
        });
        List<Map.Entry<String, NewRecordsDtoItem>> da = new LinkedList<>();
        collectData.entrySet().forEach(entry -> {
            da.add(entry);
            newCollectData.put(entry.getKey(), entry.getValue());
            oldCollect.put(entry.getKey(), entry.getValue().getT());
        });
        if (type == RecordsCollectType.chapter || type == RecordsCollectType.paperRecordPaperLibrary) {
            SysThreadPool.execute(() -> da.forEach(entry -> {
                var rankData = ExamQuestionRank.ExamQuestionRankBaseModel.builder()
                        .examId(examId)
                        .build();
                var d = entry.getValue();
                rankData.setQuestionId(entry.getKey());
                rankData.setQuestionSign(d.getSn());
                rankData.setCollectName(d.getCn());
                rankData.setCollectId(d.getCid());
                rankData.setStem(d.getSt());
                rankData.setType(d.getTr());
                rankData.setCategoryId(categoryId);
                rankData.setCollectType(d.getCt().type2CollectType());
                rankData.setCollectCount(1L);
                examQuestionRankService.addDoCount(rankData, d.getCt());
            }));
        }
        fileService.setCollectInCollectionData(userId, oldCollect, industryCode, examId, categoryId, type, id);
        fileService.setCollectRecordsData(userId, newCollectData, industryCode, examId, categoryId);
    }

    @Override
    public void addCategoryNewCollectRecords(CategoryCollectRecordsDtoItem item, RecordsCollectType type, String id, String userId, String industryCode, String examId, Long categoryId) {
        var key = String.format("%s_%s", type, id);
        var data = fileService.getCategoryCollectRecordsResult(userId, industryCode, examId, categoryId);
        var old = data.get(key);
        if (old != null && old.getTc() > item.getTc()) {
            item.setTc(old.getTc());
        }
        data.put(key, item);
        fileService.setCategoryCollectRecordsData(userId, data, industryCode, examId, categoryId);
    }

    @Override
    public void removeCategoryNewCollectRecords(RecordsCollectType type, String id, String userId, String industryCode, String examId, Long categoryId) {
        var key = String.format("%s_%s", type, id);
        var data = fileService.getCategoryCollectRecordsResult(userId, industryCode, examId, categoryId);
        data.remove(key);
        fileService.setCategoryCollectRecordsData(userId, data, industryCode, examId, categoryId);

    }

    @Override
    public void changeCategoryCountData(String userId, RecordsCountRecordDto data, String industryCode, String examId, Long categoryId) {
        if (data.empty()) return;
        var result = fileService.getCategoryCountRecordsResult(userId, industryCode, examId, categoryId);
        result.setDc(Math.max(result.getDc() + data.getDc(), 0));
        result.setEc(Math.max(result.getEc() + data.getEc(), 0));
        result.setCc(Math.max(result.getCc() + data.getCc(), 0));
        fileService.setCategoryCountRecordsData(userId, result, industryCode, examId, categoryId);
    }

    @SneakyThrows
    @Override
    public VideoRecordsDto putVideoRecordsData(String userId, Map<String, VideoRecordsDtoItem> data , String industryCode, String examId, Long categoryId, String columnId, String chapterId) {
        var records = fileService.getVideoRecordsData(userId, industryCode, examId, categoryId, columnId, chapterId);
        AtomicBoolean isAddNew = new AtomicBoolean(false);
        if(records != null  && !records.empty()){
            AtomicReference<VideoRecordsDto> recordsRef = new AtomicReference<>(records);
            data.forEach((k, v)->{
                if(recordsRef.get().get(k) == null){
                    isAddNew.set(true);
                }
                recordsRef.get().put(k,v);
            });
            data.forEach(recordsRef.get()::put);
        }else{
            records = new VideoRecordsDto();
            data.forEach(records::put);
            isAddNew.set(true);
        }
        if(isAddNew.get()){
            //更新栏目视频统计
            var countColumn = fileService.getCountColumnVideoRecordsData(userId, industryCode, examId, categoryId, columnId);
            if(countColumn == null ){
                countColumn = new CountVideoRecordsDto();
                countColumn.setVn(1);
            } else {
                countColumn.setVn(countColumn.getVn() + 1);
            }
            fileService.setCountColumnVideoRecordsData(userId,countColumn, industryCode, examId, categoryId, columnId);
            //更新章节视频统计
            List<String> allIds = chapterService.allParentIds(chapterId);
            allIds.add(chapterId);

            for (String cid : allIds) {
                RedissonLock.lock("countVideoRecordsChange:" + userId + chapterId , k -> {
                    var da = fileService.getCountVideoRecordsData( userId, industryCode, examId, categoryId, columnId, chapterId);
                    if(da == null ){
                        da = new CountVideoRecordsDto();
                        da.setVn(1);
                    }else {
                        da.setVn(Math.max(da.getVn() + 1, 0));
                    }
                    fileService.setCountVideoRecordsData(userId, da, industryCode, examId, categoryId, columnId, chapterId);
                });
            }
        }
        fileService.setVideoRecordsData(userId, records, industryCode, examId, categoryId, columnId, chapterId);
        return records;
    }
}
